﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;

namespace Noris.Schedule.Support.Sql
{
	/// <summary>
	/// Datová schránka na text SQL příkazu (i více než jednoho).
	/// Text je rozčleněn na jednotlivá slova, a ty jsou seskládány do jednotlivých SQL příkazů.
	/// S těmito SQL příkazy je možno následně manipulovat (testovat, přidávat, odebírat).
	/// </summary>
	public class SqlText : IDisposable
	{
		#region KONSTRUKCE
		/// <summary>
		/// Konstruktor je privátní, objekt se vytváří statickou metodou CreateFromText()
		/// </summary>
		private SqlText()
		{ }
		public static SqlText CreateFromText(string sqlCommand)
		{
			SqlText parse = new SqlText();
			parse.InputSqlText = sqlCommand;
			parse._WordItemList = SqlTextParser.ParseExpression(sqlCommand, parse);
			parse._ParseSql();
			return parse;
		}
		/// <summary>
		/// Úložiště seznamu všech položek vstupního textu
		/// </summary>
		private List<SqlParseItem> _WordItemList;
		/// <summary>
		/// Souhrn logických SQL příkazů (v jednom textu jich může být více).
		/// Každý jeden záznam je konkrétním potomkem třídy SqlParseCommand.
		/// </summary>
		private List<SqlParseCommand> _CommandList;
		/// <summary>
		/// IDisposable
		/// </summary>
		void IDisposable.Dispose()
		{
			this._CommandList = null;
			this._WordItemList = null;
		}
		#endregion
		#region PROPERTY
		/// <summary>
		/// Originální podoba vstupního SQL příkazu
		/// </summary>
		public string InputSqlText { get; private set; }
		/// <summary>
		/// Aktuální podoba SQL příkazu, včetně všech modifikací, SQL text
		/// </summary>
		public string OutputSqlText { get { return this._GenerateTextResultTxt(); } }
		/// <summary>
		/// Aktuální podoba SQL příkazu, včetně všech modifikací, v HTML formátu pro zobrazení
		/// </summary>
		public string OutputHtmlText { get { return this._GenerateTextResultHtml(); } }
		/// <summary>
		/// Jednotlivé textové položky SQL příkazu. Obsahuje všechny položky, včetně komentářů a blank znaků.
		/// </summary>
		public IEnumerable<SqlParseItem> WordAllList { get { return this._WordItemList; } }
		/// <summary>
		/// Jednotlivé textové položky SQL příkazu. Obsahuje pouze položky významné pro SQL server. Vynechány jsou komentáře a blank položky.
		/// </summary>
		public IEnumerable<SqlParseItem> WordSqlList { get { return this._GetSqlItemList(); } }
		/// <summary>
		/// Jednotlivé logické SQL příkazy. Vstupní text jich může obsahovat více než jeden.
		/// </summary>
		public IEnumerable<SqlParseCommand> SqlCommandList { get { return this._CommandList; } }
		#endregion
		#region LOGICKÁ ANALÝZA SQL PŘÍKAZU
		/// <summary>
		/// Provede parsování SQL příkazu = určení příkazu a jeho položek.
		/// </summary>
		private void _ParseSql()
		{
			this._CommandList = new List<SqlParseCommand>();

			Queue<SqlParseItem> sqlQueue = new Queue<SqlParseItem>(this._GetSqlItemList());
			while (sqlQueue.Count > 0)
			{	// Jeden vstupní text může obsahovat více SQL příkazů (if exists; begin; insert into; end; else; update;).
				// Každý jeden příkaz se zpracuje izolovaně:
				SqlParseCommand iParse = this._ParseSqlOneCmd(sqlQueue);
				if (iParse != null)
					this._CommandList.Add(iParse);
			}
		}
		/// <summary>
		/// Zpracuje text SQL příkazu, v dané frontě položek SQL slov.
		/// Je povinností této metody odebrat přinejmenším jednu položku.
		/// </summary>
		/// <param name="sqlQueue">Fronta SQL segmentů</param>
		private SqlParseCommand _ParseSqlOneCmd(Queue<SqlParseItem> sqlQueue)
		{
			if (sqlQueue == null || sqlQueue.Count == 0) return null;

			int sqlCount = sqlQueue.Count;

			// Zkusíme vytvořit SQL command pro slovo, které je navrchu:
			SqlParseCommand iParse = SqlParseCommand.CreateSqlParseCommand(sqlQueue.Peek());
			if (iParse != null)
			{
				iParse.SetParentText(this);
				iParse.LoadFromQueue(sqlQueue);
			}
		
			// Pokud jsem command nevytvořil, anebo ten si neodebral žádné slovo, tak první aktuální slovo přeskočím a jdu dál:
			if (sqlQueue.Count == sqlCount)
				sqlQueue.Dequeue();

			return iParse;
		}
		#endregion
		#region SKLÁDÁNÍ DAT ZPÁTKY DO SQL PŘÍKAZU
		/// <summary>
		/// Z interních dat sestaví výstupní SQL příkaz
		/// </summary>
		/// <returns></returns>
		private string _GenerateTextResultTxt()
		{
			List<SqlParseItem> formalizedList = SqlParseItem.FormalizeText(this._WordItemList);

			StringBuilder sb = new StringBuilder();
			foreach (SqlParseItem item in formalizedList)
				sb.Append(item.Text);
			return sb.ToString();
		}
		/// <summary>
		/// Z interních dat sestaví výstupní text SQL příkazu vhodný do HTML zobrazení.
		/// </summary>
		/// <returns></returns>
		private string _GenerateTextResultHtml()
		{
			List<SqlParseItem> formalizedList = SqlParseItem.FormalizeText(this._WordItemList);

			StringBuilder sb = new StringBuilder();
			sb.Append("<html><body bgcolor=\"OldLace\"><code>");
			foreach (SqlParseItem item in formalizedList)
				sb.Append(item.TextHtml);
			sb.Append("</code></body></html>");

			return sb.ToString();
		}
		#endregion
		#region POMOCNÉ METODY
		/// <summary>
		/// Vytvoří a vrátí novou položku SqlParseItem. Nikam ji neukládá, jen ji vrací.
		/// Slouží jako podpora pro SqlTextParser, který položky najde a sestaví pro ně data, a ví kam je uložit,
		/// ale potřebuje do nich vložit parenta a unikátní ID.
		/// </summary>
		/// <param name="textComponent"></param>
		/// <param name="cmdComponent"></param>
		/// <param name="text"></param>
		/// <returns></returns>
		internal SqlParseItem CreateSqlItem(SqlParseTextComponent textComponent, SqlParseCommandComponent cmdComponent, string text)
		{
			return new SqlParseItem(this, ++this._LastID, textComponent, cmdComponent, text);
		}
		/// <summary>
		/// Posledně použité ID. Příští bude mít hodnotu +1.
		/// </summary>
		private int _LastID = 0;
		/// <summary>
		/// Vrátí seznam aktuálních položek, pouze položky důležité pro SQL server
		/// </summary>
		/// <returns></returns>
		private List<SqlParseItem> _GetSqlItemList()
		{
			List<SqlParseItem> result = this._WordItemList.FindAll(item =>
				item.TextComponent == SqlParseTextComponent.SqlText ||
				item.TextComponent == SqlParseTextComponent.Delimiter ||
				item.TextComponent == SqlParseTextComponent.BracketLeft ||
				item.TextComponent == SqlParseTextComponent.BracketRight ||
				item.TextComponent == SqlParseTextComponent.String);
			return result;
		}
		#endregion
		#region PODPORA PRO MODIFIKACE TEXTU
		/// <summary>
		/// Najde a vrátí první SQL příkaz podle daného KeyWordu. Vrací jej jako typ SqlParseCommand (obecný abstraktní typ),
		/// ale uvnitř je to konkrétní potomek pro konkrétní SQL příkaz.
		/// Pokud žádný odpovídající příkaz nenajde, vrátí null.
		/// Pokud jich je víc, vrací první z nich. (Text může obsahovat více než jeden SQL příkaz tohoto typu).
		/// </summary>
		/// <param name="keyWord"></param>
		/// <returns></returns>
		public SqlParseCommand FindFirstCommand(string keyWord)
		{
			return this.SqlCommandList.FirstOrDefault(cmd => cmd.SqlCommand == keyWord);
		}
		/// <summary>
		/// Před daný prvek vloží další nové prvky textu.
		/// Pořadí vkládaných prvků zachová.
		/// Pokud tedy například chceme do pole příkazů: 0,1,2,3,4,5,6,7 vložit nové prvky a,b,c,d před prvek 4,
		/// dostaneme výsledek: 0,1,2,3,a,b,c,d,4,5,6,7.
		/// Vkládání prvků do přiměřených SqlCommandů neřeší, typicky o toto vkládání právě žádají SqlCommandy.
		/// Jiná možnost je vložit nové položky pouze do textu a pak znovu provést logické parsování.
		/// Vytvoří se aktuální stavy SqlCommandů.
		/// </summary>
		/// <param name="sqlFirst">Prvek, který v textu již existuje, a před který chceme vkládat dodaný seznam nových prvků</param>
		/// <param name="sqlItems">Prvky, které budeme vkládat (v tomto pořadí). Žádný z těchto prvků dosud nesmí být v textu obsažen.</param>
		/// <param name="skipOneBlank">Požadavek na přeskočení první položky Blank, pokud ji najdeme hned před daným prvkem</param>
		internal void InsertSqlItemsBefore(SqlParseItem sqlFirst, List<SqlParseItem> sqlItems, bool skipOneBlank)
		{
			if (sqlFirst == null)
				throw new ArgumentException("InsertSqlItemsBefore(): item «sqlFirst» is null.", "sqlFirst");

			if (sqlItems == null || sqlItems.Count == 0)
				return;

			// Najdeme index výchozího prvku
			int index = this._WordItemList.FindIndex(item => Object.ReferenceEquals(item, sqlFirst));
			if (index < 0)
				throw new ArgumentException("InsertSqlItemsBefore(): item «sqlFirst» does not exists in _WordItemList.", "sqlFirst");

			// Přeskočit Blank (doleva)?
			if (skipOneBlank)
			{
				if (index > 0 &&
					this._WordItemList[index - 1].TextComponent == SqlParseTextComponent.Blank)
					index--;
			}

			// Ověříme, že žádný prvek seznamu sqlItems dosud neexistuje v našem seznamu _WordItemList:
			foreach (SqlParseItem sqlItem in sqlItems)
			{
				if (this._WordItemList.Exists(item => Object.ReferenceEquals(item, sqlItem)))
					throw new ArgumentException("InsertSqlItemsBefore(): an item from «sqlItems» already exists in _WordItemList.", "sqlItems");
			}

			// Vlastní vkládání (insert):
			this._WordItemList.InsertRange(index, sqlItems);
		}
		/// <summary>
		/// Za daný prvek vloží další nové prvky textu.
		/// Vkládání prvků do přiměřených SqlCommandů neřeší, typicky o toto vkládání právě žádají SqlCommandy.
		/// Jiná možnost je vložit nové položky pouze do textu a pak znovu provést logické parsování.
		/// Vytvoří se aktuální stavy SqlCommandů.
		/// </summary>
		/// <param name="sqlLast">Prvek, který v textu již existuje, a za který chceme vkládat dodaný seznam nových prvků</param>
		/// <param name="sqlItems">Prvky, které budeme vkládat (v tomto pořadí). Žádný z těchto prvků dosud nesmí být v textu obsažen.</param>
		/// <param name="skipOneBlank">Požadavek na přeskočení první položky Blank, pokud ji najdeme hned za daným prvkem</param>
		internal void InsertSqlItemsAfter(SqlParseItem sqlLast, IEnumerable<SqlParseItem> sqlItems, bool skipOneBlank)
		{
			if (sqlLast == null)
				throw new ArgumentException("InsertSqlItemsAfter(): item «SqlLast» is null.", "sqlLast");

			if (sqlItems == null)
				return;

			// Najdeme index výchozího prvku
			int index = this._WordItemList.FindIndex(item => Object.ReferenceEquals(item, sqlLast));
			if (index < 0)
				throw new ArgumentException("InsertSqlItemsAfter(): item «SqlLast» does not exists in _WordItemList.", "sqlLast");

			// Přeskočit Blank (doprava)?
			if (skipOneBlank)
			{
				if (index < (this._WordItemList.Count - 1) &&
					this._WordItemList[index + 1].TextComponent == SqlParseTextComponent.Blank)
					index++;
			}

			// Ověříme, že žádný prvek seznamu sqlItems dosud neexistuje v našem seznamu _WordItemList:
			foreach (SqlParseItem sqlItem in sqlItems)
			{
				if (this._WordItemList.Exists(item => Object.ReferenceEquals(item, sqlItem)))
					throw new ArgumentException("InsertSqlItemsAfter(): an item from «sqlItems» already exists in _WordItemList.", "sqlItems");
			}

			// Vlastní vkládání (insert / append):
			if (index < (this._WordItemList.Count - 1))
				this._WordItemList.InsertRange(index + 1, sqlItems);
			else
				this._WordItemList.AddRange(sqlItems);
		}
		/// <summary>
		/// Odebere dané položky
		/// </summary>
		/// <param name="sqlItems"></param>
		public void RemoveItems(IEnumerable<SqlParseItem> sqlItems)
		{
			foreach (SqlParseItem sqlItem in sqlItems)
				this.RemoveItem(sqlItem);
		}
		/// <summary>
		/// Odebere danou položku
		/// </summary>
		/// <param name="sqlItem"></param>
		public void RemoveItem(SqlParseItem sqlItem)
		{
			int sqlIndex = this._WordItemList.FindIndex(w => Object.ReferenceEquals(w, sqlItem));
			if (sqlIndex >= 0)
				this._WordItemList.RemoveAt(sqlIndex);
		}
		#endregion
		#region STATICKÉ METODY PRO APLIKACI VÝRAZU DO SQL SELECTU
		/// <summary>
		/// Do předaného SQL selectu přidá do specifikované fráze (parametr syntax) daný text (výraz, expression).
		/// </summary>
		/// <param name="sqlText">Inp/Out syntaxe SQL příkazu</param>
		/// <param name="syntax">Syntaktická fráze, kam se má výraz vložit</param>
		/// <param name="expression">Výraz k přidání</param>
		public static void AddExpression(ref string sqlText, string command, string syntax, string expression)
		{
			using (SqlText parse = SqlText.CreateFromText(sqlText))
			{
				SqlParseCommand sqlCmd = parse.FindFirstCommand(command);
				if (sqlCmd == null)
					throw new ArgumentException("SQL command «" + command + "» does not exists in given text.", "sqlText");

				// Aplikuje se výraz do požadované syntaxe:
				sqlCmd.AddSyntaxExpression(syntax, expression, true);

				// Objekt parse je součástí příkazu command, takže aplikace výrazu do příkazu (sqlCmd) jej aplikovala i do textu v parse:
				sqlText = parse.OutputSqlText;
			}
		}
		#endregion
	}
	#region CLASS SqlTextParser : textový parser, rozdělí text na jednotlivá slova a znaky, oddělí komentáře a stringy
	/// <summary>
	/// Třída, jejímž účelem je parsovat daný text a vrátit seznam všech detekovaných položek.
	/// Volání je statické, třída si privátně vytvoří svoji instanci, kterou po výpočtu ruší.
	/// </summary>
	internal class SqlTextParser : IDisposable
	{
		#region KONSTRUKCE, PROMĚNNÉ
		/// <summary>
		/// Privátní konstruktor
		/// </summary>
		private SqlTextParser()
		{ }
		/// <summary>
		/// Dodaný text parsuje na jednotlivá slova.
		/// Předaný parametr vlastníka (owner) použije pouze jako vlastníka nově vytvářených položek, 
		/// rozhodně nové položky nevkládá do seznamu položek tohoto vlastníka (jeho seznam WordAllList zůstane nezměněn).
		/// Pokud následně bude třeba vygenerované položky vložit do seznamu vlastníka, 
		/// lze využít jeho metody InsertSqlItemsBefore() a InsertSqlItemsAfter().
		/// </summary>
		/// <param name="expression">Výraz k rozdělení na položky</param>
		/// <param name="owner">Vlastník vznikajících položek</param>
		/// <returns></returns>
		internal static List<SqlParseItem> ParseExpression(string expression, SqlText owner)
		{
			List<SqlParseItem> result = null;
			using (SqlTextParser parser = new SqlTextParser())
			{
				parser._ParseText(expression, owner);
				result = parser._WordItemList;
			}
			return result;
		}
		/// <summary>
		/// Uvolním reference
		/// </summary>
		void IDisposable.Dispose()
		{
			_WordItemList = null;
			_InputSqlText = null;
			_Owner = null;
		}
		/// <summary>
		/// Úložiště seznamu všech položek vstupního textu
		/// </summary>
		private List<SqlParseItem> _WordItemList;
		/// <summary>
		/// Pozice, kde při parsování textu začíná aktuální úsek (který se právě prohlíží).
		/// Text od této pozice se bude ukládat do nového záznamu typu SqlParseItem 
		/// do seznamu this._ItemList v metodě this._ParseCommandAddItem()
		/// </summary>
		private int _ParsingItemLastBegin;
		/// <summary>
		/// Aktuální typ textu, který se nyní parsuje. Tento segment začíná na pozici this._ParsingItemLastBegin, 
		/// a bude se ukládat do nového záznamu typu SqlParseItem 
		/// do seznamu this._ItemList v metodě this._ParseCommandAddItem()
		/// </summary>
		private SqlParseTextComponent _ItemCurrentState;
		/// <summary>
		/// Originální podoba vstupního SQL příkazu
		/// </summary>
		private string _InputSqlText;
		/// <summary>
		/// Majitel objektu, ten se vepisuje jako reference do vytvářených objektů SqlItem
		/// </summary>
		private SqlText _Owner;
		#endregion
		#region PARSOVÁNÍ TEXTU DO STRUKTURY
		/// <summary>
		/// Rozloží vstupní text na jednotlivé položky
		/// </summary>
		/// <param name="sqlCommand"></param>
		private void _ParseText(string sqlCommand, SqlText owner)
		{
			this._InputSqlText = (sqlCommand == null ? "" : sqlCommand);
			this._Owner = owner;
			this._WordItemList = new List<SqlParseItem>();
			this._ParsingItemLastBegin = 0;
			this._ItemCurrentState = SqlParseTextComponent.Blank;

			this._ParseTextTxt();
			this._ParseTextDot();

			this._InputSqlText = null;
			this._Owner = null;
		}
		/// <summary>
		/// Prvotní parsování textu, exaktní, bez doplňujících úprav.
		/// Rozdělí vstupující text na oddělená slova, komentáře, stringy, operátory, závorky, prázdné znaky.
		/// Neprovádí slučování textů typu "lcs . tabulka /* komentář */ . column" do textu "lcs.tabulka.column /* komentář */",
		/// to řeší návazná metoda _ParseTextDot().
		/// </summary>
		private void _ParseTextTxt()
		{
			string sql = this._InputSqlText;
			if (sql.Length == 0) return;

			// Konstanty:
			string commLineBegin = "--";         // Začátek řádkového komentáře
			string commLineEnd = "\r\n";         // Konec řádkového komentáře  
			string commBlockBegin = "/*";        // Začátek blokového komentáře
			string commBlockEnd = "*/";          // Konec blokového komentáře  
			char stringBeginS = '\'';             // Začátek stringu jednomístný
			string stringBeginD = "N'";          // Začátek stringu dvoumístný 
			char stringEnd = '\'';                // Konec stringu jednomístný  
			string stringSkipD = "''";           // Uvnitř stringu, dvoumístný text který se přeskakuje

			string blanks = " \t\r\n";           // Znaky prázdného textu
			string bracketsLeft = "(";           // Závorky levé
			string bracketsRight = ")";          // Závorky pravé
			string delimiters = "+-*/=!%^&*{};:,<>";  // Tyto znaky oddělují jednotlivá slova


			int lastP = (sql.Length - 1);        // Index posledního znaku
			for (int p = 0; p <= lastP; p++)
			{
				char charCurrent = sql[p];                 // Znak na aktuální pozici, načtený
				char w = charCurrent;                      // Tento znak budeme ukládat
				string charWithNext = sql.Substring(p, ((p < lastP) ? 2 : 1));    // Dva znaky (aktuální plus následující)

				if (_IsInCommandText())
				{	// Pokud jsem v textu kódu (ne komentář, ne string):
					if (charWithNext == commLineBegin)
					{	// Začátek řádkového komentáře:
						this._ParseCommandAddItem(p);      // Dosavadní položka zde končí
						this._ItemCurrentState = SqlParseTextComponent.CommentLine;
						p++;                 // Následující znak je '-', budeme jej ignorovat, jdeme na další.
					}
					else if (charWithNext == commBlockBegin)
					{	// Začátek blokového komentáře:
						this._ParseCommandAddItem(p);      // Dosavadní položka zde končí
						this._ItemCurrentState = SqlParseTextComponent.CommentBlock;
						p++;                 // Následující znak je '-', budeme jej ignorovat, jdeme na další.
					}
					else if (charCurrent == stringBeginS || charWithNext == stringBeginD)
					{	// Začátek stringu:
						this._ParseCommandAddItem(p);      // Dosavadní položka zde končí
						this._ItemCurrentState = SqlParseTextComponent.String;
						if (charWithNext == stringBeginD)  // String začíná N' => přeskočíme další znak ' :
							p++;
					}
					else if (blanks.IndexOf(charCurrent) >= 0)
					{	// Blank znak:
						if (!_IsInBlankText())             // Aktuální stav nebyl Blank?
							this._ParseCommandAddItem(p);  // Dosavadní položka zde končí
						this._ItemCurrentState = SqlParseTextComponent.Blank;
					}
					else if (bracketsLeft.IndexOf(charCurrent) >= 0)
					{	// Závorka levá:
						this._ParseCommandAddItem(p);      // Závorky dávám do výstupu po jedné, každou zvlášť (kvůli načítání úrovně)
						this._ItemCurrentState = SqlParseTextComponent.BracketLeft;
					}
					else if (bracketsRight.IndexOf(charCurrent) >= 0)
					{	// Závorka pravá:
						this._ParseCommandAddItem(p);      // Závorky dávám do výstupu po jedné, každou zvlášť (kvůli načítání úrovně)
						this._ItemCurrentState = SqlParseTextComponent.BracketRight;
					}
					else if (delimiters.IndexOf(charCurrent) >= 0)
					{	// Oddělovač:
						this._ParseCommandAddItem(p);      // Předešlý text se uloží odděleně vždy, i když by to byl rovněž Delimiter. Každý do oddělené položky.
						this._ItemCurrentState = SqlParseTextComponent.Delimiter;
					}
					else
					{	// Ostatní = SQL text (znaky, čísla, podtržítka, a další, typicky tyto znaky :   ._@#$[]""?
						if (!_IsInSqlTextText())           // Aktuální stav nebyl SqlText?
							this._ParseCommandAddItem(p);  // Dosavadní položka zde končí
						this._ItemCurrentState = SqlParseTextComponent.SqlText;
					}
				}
				else if (this._ItemCurrentState == SqlParseTextComponent.CommentLine)
				{	// Jsem v řádkovém komentáři:
					if (commLineEnd.IndexOf(charCurrent) >= 0)
					{	// Cr nebo Lf => konec řádkového komentáře:
						this._ParseCommandAddItem(p);
						this._ItemCurrentState = SqlParseTextComponent.Blank;
					}
				}
				else if (this._ItemCurrentState == SqlParseTextComponent.CommentBlock)
				{	// Jsem v blokovém komentáři:
					if (charWithNext == commBlockEnd)
					{	// Konec blokového komentáře:
						this._ParseCommandAddItem(p + 2);
						this._ItemCurrentState = SqlParseTextComponent.Blank;
						p++;                              // Příští znak je /, ten přeskočíme
					}
				}
				else if (this._ItemCurrentState == SqlParseTextComponent.String)
				{	// Jsem ve stringu:
					if (charWithNext == stringSkipD)
					{	// Typicky text  ''  uvnitř stringu značí, že jde o jeden apostrof, z hlediska syntaxe je přeskočíme oba (ten příští nebudeme vyhodnocovat):
						p++;
					}
					else if (charCurrent == stringEnd)
					{	// Zde končí string:
						this._ParseCommandAddItem(p + 1);
						this._ItemCurrentState = SqlParseTextComponent.Blank;
					}
				}
			}
			this._ParseCommandAddItem(lastP + 1);      // Poslední položka zde končí (pokud má nenulovou délku).
		}
		/// <summary>
		/// Sekundární parsování, kdy proběhnou drobné úpravy načteného textu.
		/// Například: spojování oddělených prvků SqlText, pokud je mezi nimi pouze blank a tečka, například:
		/// "lcs"; " "; "."; " "; "tabulka" spojí do jedné položky "lcs.tabulka".
		/// Rovněž tak připojím oddělený znak # k příštímu SqlTextu, protože jde o začátek jména TEMP tabulky.
		/// Prvotní parsování odděluje texty v místě blank, ale když je mezi dvěma SqlText tečka, pak patří k sobě.
		/// </summary>
		private void _ParseTextDot()
		{
			for (int t = 0; t < this._WordItemList.Count; t++)
			{
				SqlParseItem item = this._WordItemList[t];
				if (item.IsSystemName)
				{	// Pokud je v položce item prvek typu SqlText se systémovým jménem, možná by jej šlo spojit s následujícími prvky:
					string joinText = item.Text;
					int joinUpTo = -1;
					for (int q = t + 1; q < this._WordItemList.Count; q++)
					{
						SqlParseItem test = this._WordItemList[q];  // Co je tam dalšího
						if (test.IsSqlVoid) continue;           // Komentáře a blank prvky přeskočíme
						if (!test.IsDotSystemName) break;       // Cokoli jiného než [tečka]systémové jméno neberu, ukončí se spojování textu.

						// Máme SqlText, nyní zjistíme zda jde s předešlým SqlTextem spojit tak, aby spojem byla právě jedna tečka:
						int endDots = GetEndCharCount(joinText, '.');
						int beginDots = GetBeginCharCount(test.Text, '.');
						if (endDots + beginDots == 1)
						{
							joinText += test.Text;
							joinUpTo = q;
							continue;
						}
						break;                               // Nejde to spojit, skončíme.
					}

					// Pokud jsme něco spojili, vepíšeme to do seznamu _ItemList:
					if (joinUpTo > 0)
					{
						// a) vložit spojený text do výchozího prvku:
						item.SetText(joinText);

						// b) zneplatníme prvky, které jsme po cestě připojili (jde o prvky typu SqlText až do prvku joinUpTo včetně):
						for (int q = t + 1; q <= joinUpTo; q++)
						{
							SqlParseItem test = this._WordItemList[q];
							if (test.TextComponent == SqlParseTextComponent.SqlText)
								test.SetText("", SqlParseTextComponent.Blank);
						}
					}
				}
			}
		}
		/// <summary>
		/// Vrací true, pokud status odpovídá pozici v holém SQL textu (Blank, SqlText, Delimiter, BracketLeft/Right).
		/// Vrací false, pokud status je komentář nebo string.
		/// </summary>
		/// <param name="state"></param>
		/// <returns></returns>
		private bool _IsInCommandText()
		{
			SqlParseTextComponent state = this._ItemCurrentState;
			return (state == SqlParseTextComponent.Blank
				|| state == SqlParseTextComponent.SqlText
				|| state == SqlParseTextComponent.Delimiter
				|| state == SqlParseTextComponent.BracketLeft
				|| state == SqlParseTextComponent.BracketRight);
		}
		/// <summary>
		/// Vrací true, pokud status je Blank
		/// </summary>
		/// <returns></returns>
		private bool _IsInBlankText()
		{
			return (this._ItemCurrentState == SqlParseTextComponent.Blank);
		}
		/// <summary>
		/// Vrací true, pokud status je BracketLeft/Right
		/// </summary>
		/// <returns></returns>
		private bool _IsInBracketText()
		{
			return (this._ItemCurrentState == SqlParseTextComponent.BracketLeft
				|| this._ItemCurrentState == SqlParseTextComponent.BracketRight);
		}
		/// <summary>
		/// Vrací true, pokud status je BracketLeft/Right
		/// </summary>
		/// <returns></returns>
		private bool _IsInSqlTextText()
		{
			return (this._ItemCurrentState == SqlParseTextComponent.SqlText);
		}
		/// <summary>
		/// Vrací true, pokud status je String
		/// </summary>
		/// <returns></returns>
		private bool _IsInStringText()
		{
			return (this._ItemCurrentState == SqlParseTextComponent.String);
		}
		/// <summary>
		/// Do seznamu položek přidá další položku ze vstupního textu, 
		/// </summary>
		/// <param name="end"></param>
		private void _ParseCommandAddItem(int end)
		{
			if (end <= this._ParsingItemLastBegin) return;           // Žádný kousek textu: končíme.

			// Text:
			string text = this._InputSqlText.Substring(this._ParsingItemLastBegin, (end - this._ParsingItemLastBegin));
			this._WordItemList.Add(this._Owner.CreateSqlItem(this._ItemCurrentState, SqlParseCommandComponent.None, text));
			this._ParsingItemLastBegin = end;
		}
		#endregion
		#region POMOCNÉ PUBLIC STATIC METODY
		/// <summary>
		/// Vrátí počet, kolik znaků testChar je od začátku daného textu. Jakýkoli jiný znak ukončí nápočet.
		/// </summary>
		/// <param name="text"></param>
		/// <param name="testChar"></param>
		public static int GetBeginCharCount(string text, char testChar)
		{
			if (text == null) return 0;
			int result = 0;
			for (int i = 0; i < text.Length; i++)
			{
				if (text[i] != testChar) return result;
				result++;
			}
			return result;
		}
		/// <summary>
		/// Vrátí počet, kolik znaků testChar je od začátku daného textu. Jakýkoli jiný znak ukončí nápočet.
		/// </summary>
		/// <param name="text"></param>
		/// <param name="testChar"></param>
		public static int GetEndCharCount(string text, char testChar)
		{
			if (text == null) return 0;
			int result = 0;
			for (int i = text.Length - 1; i >= 0; i--)
			{
				if (text[i] != testChar) return result;
				result++;
			}
			return result;
		}
		#endregion
	}
	#endregion
	#region CLASS SqlParseItem : jedna položka textu SQL příkazu (včetně komentářů)
	/// <summary>
	/// Jedna položka v seznamu součástí SQL příkazu (text příkazu, komentář, 
	/// </summary>
	public class SqlParseItem
	{
		#region KONSTRUKCE, ZÁKLADNÍ PROPERTY
		public SqlParseItem(SqlText parentText, int itemId, SqlParseTextComponent textComponent, SqlParseCommandComponent cmdComponent, string text)
		{
			this.ParentText = parentText;
			this.ItemId = itemId;
			this.TextComponent = textComponent;
			this.CmdComponent = cmdComponent;
			this.Text = text;
			this.BracketLevel = 0;
			this.KeyWord = (textComponent == SqlParseTextComponent.SqlText ? text.ToUpper() : null);
		}
		public override string ToString()
		{
			return this.TextComponent.ToString() + ": " + this.Text;
		}
		/// <summary>
		/// ID položky. Dokud žije, je konstantní. Seznam se může měnit, ale číslo ID zůstává.
		/// </summary>
		public int ItemId { get; private set; }
		/// <summary>
		/// Reference na SQL text, v němž se tento příkaz našel.
		/// Umožní každé položce, aby si mohla něco před/za sebe přidat.
		/// Nikdy není null, každá položka textu pochází z určitého SQL textu celého příkazu.
		/// </summary>
		public SqlText ParentText { get; private set; }
		/// <summary>
		/// Reference na položku syntaxe, ve které se toto slovo nachází.
		/// Po parsování textu je null, po parsování SQL příkazů je nastaveno na tu položku syntaxe, do které přísluší.
		/// Blank a Comment texty nepatří nikam.
		/// </summary>
		public SqlParseSyntaxItem ParentSyntaxItem { get; private set; }
		/// <summary>
		/// Typ položky v textu, hrubé členění
		/// </summary>
		public SqlParseTextComponent TextComponent { get; private set; }
		/// <summary>
		/// Typ položky ve vlastním SQL  příkazu, členění dle klíčových slov, literál, proměnná, ...
		/// </summary>
		public SqlParseCommandComponent CmdComponent { get; private set; }
		/// <summary>
		/// Plný text položky, případně i včetně krajních znaků (apostrofy u stringu, oodělovače komentářů)
		/// </summary>
		public string Text { get; private set; }
		/// <summary>
		/// Úroveň závorek, na které se nachází tato položka.
		/// Není nastavena pro komentáře a pro Blank prvky (Space, Tab, Cr, Lf).
		/// Text mimo závorky má level 0.
		/// První otevírací závorka má level = 1.
		/// Poslední zavírací závorka má také level = 1.
		/// </summary>
		public int BracketLevel { get; private set; }
		/// <summary>
		/// ID SQL příkazu, protože vstupní text může obsahovat příkazů víc než jeden.
		/// Pokud je 0, pak toto slovo není součástí žádného příkazu.
		/// To se obecně týká všech komentářů a elementů Blank (Cr, Lf, Space, Tab).
		/// </summary>
		public int CommandId { get; private set; }
		/// <summary>
		/// Pokud je v tomto objektu text typu SqlText, vrátí this.Text, jinak vrátí empty string (ne null).
		/// </summary>
		public string SqlText { get { return (this.TextComponent == SqlParseTextComponent.SqlText ? this.Text : ""); } }
		/// <summary>
		/// Text klíčového slova a modifikátoru, Upper(). null pro všechny ostatní typy komponent.
		/// </summary>
		public string KeyWord { get; private set; }
		/// <summary>
		/// Text položky, upper, trim
		/// </summary>
		public string TextTrim { get { return this.Text.Trim().ToUpper(); } }
		/// <summary>
		/// Vloží danou hodnotu do this.ParentSyntaxItem. 
		/// Navazuje vztah na parent položku syntaxe, do které toto slovo patří.
		/// Například všechna slova "LEFT OUTER JOIN lcs.tabulka t on t.cislo_subjektu = po.zdroj" patří do syntaktického segmentu "FROM".
		/// </summary>
		/// <param name="parentSyntaxItem"></param>
		internal void SetParentSyntaxItem(SqlParseSyntaxItem parentSyntaxItem)
		{
			this.ParentSyntaxItem = parentSyntaxItem;
		}
		/// <summary>
		/// Vloží do Text daný text
		/// </summary>
		/// <param name="text"></param>
		internal void SetText(string text)
		{
 			this.Text = text;
		}
		/// <summary>
		/// Vloží do Text daný text, a TextComponent
		/// </summary>
		/// <param name="text"></param>
		internal void SetText(string text, SqlParseTextComponent textComponent)
		{
 			this.Text = text;
			this.TextComponent = textComponent;
		}
		/// <summary>
		/// Vloží do this daný druh TextComponent
		/// </summary>
		/// <param name="text"></param>
		internal void SetTextComponent(SqlParseTextComponent textComponent)
		{
			this.TextComponent = textComponent;
		}
		/// <summary>
		/// Do položky vloží její úroveň závorek.
		/// Text mimo závorky má level 0.
		/// První otevírací závorka má level = 1.
		/// Poslední zavírací závorka má také level = 1.
		/// </summary>
		/// <param name="bracketLevel"></param>
		internal void SetBracketLevel(int bracketLevel)
		{
			if (bracketLevel >= 0)
				this.BracketLevel = bracketLevel;
		}
		/// <summary>
		/// Do položky vloží ID SQL commandu
		/// </summary>
		/// <param name="commandId"></param>
		internal void SetCommandId(int commandId)
		{
			this.CommandId = commandId;
		}
		/// <summary>
		/// Do položky vloží SQL typ položky. Vkládá se jen do SQL významných položek, ne do komentářů a blank prvků.
		/// </summary>
		/// <param name="cmdComponent"></param>
		internal void SetCmdComponent(SqlParseCommandComponent cmdComponent)
		{
			this.CmdComponent = cmdComponent;
		}
		/// <summary>
		/// Do položky vloží SQL přiměřený typ položky pro část příkazu "Content" (nejde o klíčové slovo).
		/// Obsah rozpozná podle typu textu.
		/// </summary>
		internal void SetCmdComponentContent()
		{
			switch (this.TextComponent)
			{
				case SqlParseTextComponent.Removed:
					this.CmdComponent = SqlParseCommandComponent.None;
					break;
				case SqlParseTextComponent.Blank:
					this.CmdComponent = SqlParseCommandComponent.None;
					break;
				case SqlParseTextComponent.BracketLeft:
					this.CmdComponent = SqlParseCommandComponent.BracketLeft;
					break;
				case SqlParseTextComponent.BracketRight:
					this.CmdComponent = SqlParseCommandComponent.BracketRight;
					break;
				case SqlParseTextComponent.CommentBlock:
					this.CmdComponent = SqlParseCommandComponent.None;
					break;
				case SqlParseTextComponent.CommentLine:
					this.CmdComponent = SqlParseCommandComponent.None;
					break;
				case SqlParseTextComponent.Delimiter:
					this.CmdComponent = SqlParseCommandComponent.Operator;
					break;
				case SqlParseTextComponent.SqlText:
					this.CmdComponent = _GetCmdComponentByText(this);
					break;
				case SqlParseTextComponent.String:
					this.CmdComponent = SqlParseCommandComponent.String;
					break;
			}
		}
		/// <summary>
		/// Vrátí druh SqlText, když to není ani KeyWord ani String (podle obsahu číslic je to buď Numeric anebo Parameter)
		/// </summary>
		/// <param name="sqlItem"></param>
		/// <returns></returns>
		private static SqlParseCommandComponent _GetCmdComponentByText(SqlParseItem sqlItem)
		{
			string text = sqlItem.Text;
			string numerics = "0123456789";
			bool isNumeric = (text.Length > 0);
			foreach (char n in text)
			{
				if (numerics.IndexOf(n) < 0)
				{
					isNumeric = false;
					break;
				}
			}
			return (isNumeric ? SqlParseCommandComponent.Numeric : SqlParseCommandComponent.Parameter);
		}
		#endregion
		#region PROPERTY TYPU IsAny
		/// <summary>
		/// Příznak, že tato položka je z hlediska SQL serverů významná
		/// (jde o položky typu SqlText, BracketLeft, BracketRight, Delimiter, String)
		/// </summary>
		public bool IsSqlSignificant { get { SqlParseTextComponent t = this.TextComponent; return (t == SqlParseTextComponent.SqlText || t == SqlParseTextComponent.BracketLeft || t == SqlParseTextComponent.BracketRight || t == SqlParseTextComponent.Delimiter || this.TextComponent == SqlParseTextComponent.String); } }
		/// <summary>
		/// Příznak, že tato položka je z hlediska SQL serverů bezvýznamná
		/// (jde o jiné položky než typu SqlText, BracketLeft, BracketRight, Delimiter, String)
		/// </summary>
		public bool IsSqlVoid { get { return !this.IsSqlSignificant; } }
		/// <summary>
		/// Příznak, že tato položka je typu SqlText a její text by mohl být systémové jméno, 
		/// tečky se nepřipouští.
		/// </summary>
		public bool IsSystemName
		{
			get
			{
				if (this.TextComponent != SqlParseTextComponent.SqlText) return false;
				string text = this.Text.ToLower();
				return _IsSystemName(text, false);
			}
		}
		/// <summary>
		/// Příznak, že tato položka je typu SqlText a její text by mohl být systémové jméno, 
		/// popřípadě začíná tečkou a za ní je systémové jméno.
		/// </summary>
		public bool IsDotSystemName
		{
			get
			{
				if (this.TextComponent != SqlParseTextComponent.SqlText) return false;
				string text = this.Text.ToLower();
				if (text[0] == '.')
				{	// Pokud je na začátku tečka:
					if (text.Length == 1) return true;                         // Jen tečka: vracím true
					text = text.Substring(1);                                  // Tečka a něco: otestuji to za tečkou.
				}
				return _IsSystemName(text, true);
			}
		}
		/// <summary>
		/// Příznak, že položka je KeyWord
		/// </summary>
		public bool IsKeyWord { get { return (this.TextComponent == SqlParseTextComponent.SqlText && this.CmdComponent == SqlParseCommandComponent.KeyWord); } }
		/// <summary>
		/// Vrací true, pokud tato položka je SqlText, KeyWord, a má BracketLevel == 0
		/// </summary>
		internal bool IsTopKeyWord
		{ get { return this.TextComponent == SqlParseTextComponent.SqlText && this.CmdComponent == SqlParseCommandComponent.KeyWord && this.BracketLevel == 0; } }
		/// <summary>
		/// Vrací true, pokud tato položka je SqlText a má BracketLevel == 0
		/// </summary>
		internal bool IsTopSqlText
		{ get { return this.TextComponent == SqlParseTextComponent.SqlText && this.BracketLevel == 0; } }
		/// <summary>
		/// Je daný text systémové jméno?
		/// </summary>
		/// <param name="text"></param>
		/// <returns></returns>
		private static bool _IsSystemName(string text, bool withInnerDot)
		{
			if (text == null || text.Length == 0) return false;
			string systemFirstChars = "_abcdefghijklmnopqrstuvwxyz";           // Povolené znaky na první pozici
			if (systemFirstChars.IndexOf(text[0]) < 0) return false;           // Znak na první pozici není povolen?

			int textLength = text.Length;
			if (textLength == 1) return true;

			string systemOtherChars = systemFirstChars + "0123456789" + (withInnerDot ? "." : "");  // Povolené znaky na druhé a další pozici
			for (int p = 1; p < textLength; p++)
			{
				if (systemOtherChars.IndexOf(text[p]) < 0) return false;
			}
			return true;
		}
		#endregion
		#region KONVERZE DO HTML a do VISUAL
		/// <summary>
		/// Plný text položky formátovaný pro HTML zobrazení
		/// </summary>
		public string TextHtml { get { return _GetTextHtml(); } }
		/// <summary>
		/// Vizualizovatelný text, jsou vidět znaky Cr, Lf, Space, Tab
		/// </summary>
		public string TextVisual { get { return _GetTextVisual(); } }
		/// <summary>
		/// Vrátí plný text položky formátovaný pro HTML zobrazení
		/// </summary>
		/// <returns></returns>
		private string _GetTextHtml()
		{
			string result = this.Text;
			result = result.Replace("<", "&lt;");
			result = result.Replace(">", "&gt;");
			result = result.Replace("\r", "<br>");
			result = result.Replace("\n", "");
			result = result.Replace(" ", "&nbsp;");
			result = result.Replace("\t", "&nbsp;&nbsp;&nbsp;&nbsp;");
			switch (this.TextComponent)
			{
				case SqlParseTextComponent.Removed:
					result = "";
					break;
				case SqlParseTextComponent.Blank:
					break;
				case SqlParseTextComponent.BracketLeft:
				case SqlParseTextComponent.BracketRight:
				case SqlParseTextComponent.Delimiter:
					result = "<font color=\"DarkOrchid\">" + result + "</font>";
					break;
				case SqlParseTextComponent.CommentBlock:
				case SqlParseTextComponent.CommentLine:
					result = "<i><font color=\"DimGrey\">" + result + "</font></i>";
					break;
				case SqlParseTextComponent.String:
					result = "<font color=\"Red\">" + result + "</font>";
					break;
				case SqlParseTextComponent.SqlText:
					result = _GetTextHtmlSqlText(result);
					break;
			}
			return result;
		}
		/// <summary>
		/// Vrátí plný text položky formátovaný pro HTML zobrazení, výhradně jen pro položku typu SqlText
		/// </summary>
		/// <returns></returns>
		private string _GetTextHtmlSqlText(string result)
		{
			switch (this.CmdComponent)
			{
				case SqlParseCommandComponent.KeyWord:
					return "<font color=\"Blue\">" + result + "</font>";
				case SqlParseCommandComponent.BracketLeft:
					return "<font color=\"DarkOrchid\">" + result + "</font>";
				case SqlParseCommandComponent.BracketRight:
					return "<font color=\"DarkOrchid\">" + result + "</font>";
				case SqlParseCommandComponent.Numeric:
					return "<font color=\"Black\">" + result + "</font>";
				case SqlParseCommandComponent.Literal:
				case SqlParseCommandComponent.None:
				case SqlParseCommandComponent.Operator:
				case SqlParseCommandComponent.Parameter:
					return "<font color=\"DarkGreen\">" + result + "</font>";
			}
			return "<font color=\"Black\">" + result + "</font>";
		}
		/// <summary>
		/// Vrátí vizualizovatelný text, jsou vidět znaky Cr, Lf, Space, Tab
		/// </summary>
		/// <returns></returns>
		private string _GetTextVisual()
		{
			string result = this.Text;
			result = result.Replace(" ", "{Space}");
			result = result.Replace("\r", "{Cr}");
			result = result.Replace("\n", "{Lf}");
			result = result.Replace("\t", "{Tab}");
			return result;
		}
		#endregion
		#region SLOUČENÍ SEZNAMU POLOŽEK DO KOREKTNÍHO SQL TEXTU
		/// <summary>
		/// Sloučí položky z daného seznamu do korektního SQL textu
		/// </summary>
		/// <param name="list"></param>
		/// <returns></returns>
		internal static string JoinToSqlText(IEnumerable<SqlParseItem> list)
		{
			List<SqlParseItem> formalizedList = FormalizeText(list, true, false, false);

			StringBuilder sb = new StringBuilder();
			foreach (SqlParseItem item in formalizedList)
				sb.Append(item.Text);

			return sb.ToString();
		}
		/// <summary>
		/// Sloučí položky z daného seznamu do korektního SQL textu
		/// </summary>
		/// <param name="list"></param>
		/// <returns></returns>
		internal static List<SqlParseItem> FormalizeText(IEnumerable<SqlParseItem> list)
		{
			return FormalizeText(list, true, false, false);
		}
		/// <summary>
		/// Sloučí položky z daného seznamu do korektního SQL textu
		/// </summary>
		/// <param name="list"></param>
		/// <returns></returns>
		internal static List<SqlParseItem> FormalizeText(IEnumerable<SqlParseItem> list, bool withComment, bool commandToNewLine, bool syntaxToNewLine)
		{
			List<SqlParseItem> result = new List<SqlParseItem>();

			SqlParseItem sqlPrev = null;
			foreach (SqlParseItem sqlItem in list)
			{
				// Vyřazené položky přeskakuji:
				if (sqlItem.TextComponent == SqlParseTextComponent.Removed)
					continue;

				// Je zapotřebí něco mezi ty dvě položky přidat?
				SqlParseItem addItem = SqlParseItem.GetNeedSpaceBetween(sqlPrev, sqlItem);
				if (addItem != null)
					result.Add(addItem);

				// Přidáme naší položku:
				result.Add(sqlItem);

				// Příště bude aktuální položka minulostí:
				sqlPrev = sqlItem;
			}

			return result;
		}
		#endregion
		#region DETEKCE POTŘEBY VKLÁDÁNÍ MEZER MEZI POLOŽKY
		/// <summary>
		/// Vrátí položku (typu Blank, s obsahem Space nebo NewLine), pokud je zapotřebí takovou vložit mezi dva dané prvky.
		/// Pokud není zapotřebí vkládat žádnou položku, vrátí null.
		/// </summary>
		/// <param name="prev">Předešlá položka</param>
		/// <param name="next">Následující položka</param>
		/// <returns></returns>
		internal static SqlParseItem GetNeedSpaceBetween(SqlParseItem prev, SqlParseItem next)
		{
			if (prev == null || next == null) return null;       // Pokud jedna ze stran neexistuje, není třeba vložkovat mezerou.

			SqlParseTextComponent prevCmp = prev.TextComponent;
			bool prevIsKey = prev.IsKeyWord;
			bool prevIsSgf = prev.IsSqlSignificant;
			bool prevIsTxt = (prev.TextComponent == SqlParseTextComponent.SqlText);
			bool prevIsDlm = (prev.TextComponent == SqlParseTextComponent.Delimiter);
			SqlParseTextComponent nextCmp = next.TextComponent;
			bool nextIsKey = next.IsKeyWord;
			bool nextIsSgf = next.IsSqlSignificant;
			bool nextIsTxt = (next.TextComponent == SqlParseTextComponent.SqlText);
			bool nextIsDlm = (next.TextComponent == SqlParseTextComponent.Delimiter);

			// NewLine se vkládá tehdy, když předešlá položka je řádkový komentář, a následující položka je jakákoli jiná než Blank NewLine:
			bool nextIsEol = (nextCmp == SqlParseTextComponent.Blank && (next.Text.IndexOfAny(Environment.NewLine.ToCharArray()) >= 0));
			if (prev.TextComponent == SqlParseTextComponent.CommentLine && !nextIsEol)
				return _GetNewBlankItem(prev, Environment.NewLine);

			// Mezeru vložím tehdy, když jdou za sebou dva SQL texty:
			if (prevCmp == SqlParseTextComponent.SqlText && nextCmp == SqlParseTextComponent.SqlText)
				return _GetNewBlankItem(prev, " ");

			// Mezeru vložím tehdy, když vlevo je KeyWord a za ním je string nebo něco významuplného:
			if (prevIsKey && nextIsSgf)
				return _GetNewBlankItem(prev, " ");

			// Mezeru vložím tehdy, když vpravo je KeyWord a před ním je string nebo něco významuplného:
			if (prevIsSgf && nextIsKey)
				return _GetNewBlankItem(prev, " ");

			// Mezeru nevložím, když za sebou následuje {{ nebo {SqlText, anebo naopak }}   SqlText} :
			if (prevIsDlm && prev.Text == "{" && ((nextIsDlm && (next.Text == "{" || next.Text == "}")) || next.TextComponent == SqlParseTextComponent.SqlText))
				return null;
			if (nextIsDlm && next.Text == "}" && ((prevIsDlm && (prev.Text == "{" || prev.Text == "}")) || prev.TextComponent == SqlParseTextComponent.SqlText))
				return null;

			// Mezeru nemusím vkládat, pokud je nalevo cokoliv, a napravo delimiter čárka:
			if (nextIsDlm && next.Text == ",")
				return null;

			// Mezeru vložím tehdy, když na jedné straně je SqlText a vedle je Delimiter:
			if ((prevIsTxt && nextIsDlm) || (prevIsDlm && nextIsTxt))
				return _GetNewBlankItem(prev, " ");

			return null;
		}
		/// <summary>
		/// Vrátí nový Blank prvek s daným textem.
		/// </summary>
		/// <param name="sqlItem"></param>
		/// <param name="text"></param>
		/// <returns></returns>
		private static SqlParseItem _GetNewBlankItem(SqlParseItem sqlItem, string text)
		{
			return sqlItem.ParentText.CreateSqlItem(SqlParseTextComponent.Blank, SqlParseCommandComponent.None, text);
		}
		#endregion
		#region STATIC SERVIS
		/// <summary>
		/// Metoda zjistí, zda daný seznam obsahuje alespoň jednu signifikantní položku.
		/// Pokud ano, vrátí true.
		/// </summary>
		/// <param name="sqlItems"></param>
		/// <returns></returns>
		internal static bool IsListSignificant(IEnumerable<SqlParseItem> sqlItems)
		{
			if (sqlItems == null)
				return false;

			foreach (SqlParseItem sqlItem in sqlItems)
			{
				if (sqlItem.IsSqlSignificant)
					return true;
			}
			return false;
		}
		/// <summary>
		/// Z dané fronty SQL položek vyjme všechny úvodní mezery
		/// </summary>
		/// <param name="sqlQueue"></param>
		internal static void SkipVoid(Queue<SqlParseItem> sqlQueue)
		{
			while (sqlQueue.Count > 0)
			{
				SqlParseItem sqlItem = sqlQueue.Peek();
				if (sqlItem.IsSqlSignificant)
					break;
				sqlQueue.Dequeue();
			}
		}
		/// <summary>
		/// V dané frontě SQL položek najde první, která je klíčovým slovem (v první úrovni) s daným textem.
		/// Daný text může být kterýkoli z předaných položek.
		/// Prohlíží si frontu, a dokud na vrchní pozici není požadovaná položka, tak vrchní pozici zahodí.
		/// </summary>
		/// <param name="sqlQueue">Fronta položek SQL příkazu</param>
		/// <param name="keywords">Soupis povolených klíčových slov (musí být Trim().ToUpper() !!!)</param>
		internal static void FindFirstKeywordInQueue(Queue<SqlParseItem> sqlQueue, params string[] keywords)
		{
			if (sqlQueue == null || sqlQueue.Count == 0) return;
			List<string> keyList = new List<string>(keywords);
			FindFirstKeywordInQueue(sqlQueue, keyList);
		}
		/// <summary>
		/// V dané frontě SQL položek najde první, která je SqlText slovem v první úrovni, s některým klíčovým textem.
		/// Klíčový text může být kterákoli z předaných položek.
		/// Prohlíží si frontu, a dokud na vrchní pozici není požadovaná položka, tak vrchní pozici zahodí a jde ve frontě dál.
		/// </summary>
		/// <param name="sqlQueue">Fronta položek SQL příkazu</param>
		/// <param name="keyList">Seznam povolených klíčových slov (musí být Trim().ToUpper() !!!)</param>
		internal static void FindFirstKeywordInQueue(Queue<SqlParseItem> sqlQueue, List<string> keyList)
		{
			while (sqlQueue.Count > 0)
			{
				SqlParseItem sqlItem = sqlQueue.Peek();
				if (sqlItem.IsTopSqlText && keyList.Contains(sqlItem.KeyWord))
					break;

				// Položka na vrchu seznamu nevyhovuje, zahodím ji:
				sqlQueue.Dequeue();
			}
		}
		/// <summary>
		/// Z fronty načte a vrátí výraz.
		/// Začíná aktuální pozicí, a hledá konec = 
		/// Pokud najde levou závorku, jednoduše vyhledá a přidá párovou koncovou závorku, a řeší až další texty.
		/// Pokud se pohybuji mimo závorky, pak přidávám další položky, ale když najdu SqlText s některým z klíčových slov, tak to slovo už nepřidám.
		/// </summary>
		/// <param name="sqlQueue"></param>
		/// <returns></returns>
		internal static IEnumerable<SqlParseItem> LoadExpressionFromQueue(Queue<SqlParseItem> sqlQueue, List<string> stopKeyWords)
		{
			List<SqlParseItem> result = new List<SqlParseItem>();

			while (sqlQueue.Count > 0)
			{
				SqlParseItem sqlItem = sqlQueue.Peek();

				if (sqlItem.TextComponent == SqlParseTextComponent.BracketLeft)
				{
					result.AddRange(LoadBracketFromQueue(sqlQueue));
					continue;
				}
				string sqlText = sqlItem.SqlText.ToUpper();
				if (stopKeyWords.Contains(sqlText))
					break;

				result.Add(sqlItem);
				sqlQueue.Dequeue();
			}
			return result;
		}
		/// <summary>
		/// Z fronty načte a vrátí závorkovaný obsah.
		/// Podmínka: fronta nyní stojí na BracketLeft.
		/// Pokud ne, pak se fronta neposune a vrátí se empty list.
		/// Po skončení načítání obsahu závorky stojí fronta na první položce za koncovou závorkou.
		/// </summary>
		/// <param name="sqlQueue"></param>
		/// <returns></returns>
		internal static IEnumerable<SqlParseItem> LoadBracketFromQueue(Queue<SqlParseItem> sqlQueue)
		{
			List<SqlParseItem> result = new List<SqlParseItem>();

			if (sqlQueue.Count > 0)
			{
				SqlParseItem firstItm = sqlQueue.Peek();         // Aktuální položka: nejdřív se jen podívám, jestli to je levá závorka
				if (firstItm.TextComponent == SqlParseTextComponent.BracketLeft)
				{	// Je to levá závorka: dám ji do výstupu a z fronty ji sežeru:
					result.Add(firstItm);
					sqlQueue.Dequeue();
					while (sqlQueue.Count > 0)
					{	// Dokud na vstupu něco je:
						SqlParseItem sqlItem = sqlQueue.Dequeue();
						result.Add(sqlItem);                     // Přidávám do výstupu všechno, i koncovou pravou závorku
						if (sqlItem.TextComponent == SqlParseTextComponent.BracketRight && sqlItem.BracketLevel == firstItm.BracketLevel)
							break;                               // A pokud jde o párovou pravou závorku, skončím.
					}
				}
			}
			return result;
		}
		/// <summary>
		/// Vrátí true, pokud daný seznam slov obsahuje některé s daným Sqltextem.
		/// Prohledává tedy jen položky typu SqlText, v základní úrovni textu (BracketLevel == 0).
		/// Hledá Trim().ToUpper()
		/// </summary>
		/// <param name="list">Seznam položek</param>
		/// <param name="keywords">Hledaná slova</param>
		/// <returns></returns>
		internal static bool ContainSqlText(IEnumerable<SqlParseItem> sqlItems, params string[] keywords)
		{
			return ContainSqlText(sqlItems, new List<string>(keywords));
		}
		/// <summary>
		/// Vrátí true, pokud daný seznam slov obsahuje některé s daným Sqltextem.
		/// Prohledává tedy jen položky typu SqlText, v základní úrovni textu (BracketLevel == 0).
		/// Hledá Trim().ToUpper()
		/// </summary>
		/// <param name="list">Seznam položek</param>
		/// <param name="keywords">Hledaná slova</param>
		/// <returns></returns>
		internal static bool ContainSqlText(IEnumerable<SqlParseItem> sqlItems, IEnumerable<string> keywords)
		{
			if (sqlItems == null) return false;
			if (keywords == null) return false;
			Hashtable keys = new Hashtable();
			foreach (string key in keywords)
			{
				string keyUp = key.Trim().ToUpper();
				if (!keys.ContainsKey(keyUp))
					keys.Add(keyUp, null);
			}
			if (keys.Count == 0) return false;

			foreach (SqlParseItem sqlItem in sqlItems)
			{
				if (sqlItem.TextComponent == SqlParseTextComponent.SqlText && sqlItem.BracketLevel == 0)
				{
					if (keys.ContainsKey(sqlItem.Text.Trim().ToUpper()))
						return true;
				}
			}
			return false;
		}
		#endregion
	}
	#endregion
	#region ENUMS SqlParseTextComponent, SqlParseCommandComponent
	/// <summary>
	/// Druh položky v textu SQL příkazu, základní hrubé členění (komentáře, texty, stringy)
	/// </summary>
	public enum SqlParseTextComponent
	{
		/// <summary>
		/// Vyjmutý prvek, odstraněný, neviditelný
		/// </summary>
		Removed = 1,
		/// <summary>
		/// Prázdný text (mezera, tabulátor, Cr, Lf)
		/// - explicitně zachycená ze vstupního textu, umožní složit nový příkaz víceméně v původní podobě
		/// Pokud se některý z Blank znaků objeví ve stringu nebo v blokovém komentáři, bude zahrnut v něm.
		/// </summary>
		Blank,
		/// <summary>
		/// Řádkový komentář, včetně úvodního textu  --
		/// </summary>
		CommentLine,
		/// <summary>
		/// Blokový komentář, včetně úvodního a koncového textu  /*  xxx  */
		/// Součástí blokového komentáře jsou i znaky Cr a Lf.
		/// </summary>
		CommentBlock,
		/// <summary>
		/// Text SQL příkazu nebo proměnných nebo výrazů, kromě stringu (ty by mohly působit problémy při vyhodnocování)
		/// Text je rozdělen na jednotlivá slova nebo položky výrazů. Text tedy nikdy neobsahuje znak, které jsou Blank
		/// (ty jsou ukládány do samostatných položek).
		/// </summary>
		SqlText,
		/// <summary>
		/// Oddělovač SQL textu, typicky operátor. Z hlediska skladby příkazu jej nelze pominout.
		/// Jde o znaky: +-*/=!%^&*{};:, menší větší
		/// </summary>
		Delimiter,
		/// <summary>
		/// Závorka levá, otevírací "(", každá je uvedena jednotlivě.
		/// </summary>
		BracketLeft,
		/// <summary>
		/// Závorka pravá, zavírací ")", každá je uvedena jednotlivě.
		/// </summary>
		BracketRight,
		/// <summary>
		/// String, včetně krajních apostrofů.
		/// Součástí stringu jsou i znaky Cr a Lf.
		/// </summary>
		String
	}
	/// <summary>
	/// Druh položky v rámci vlastního SQL příkazu (klíčové slovo, modifikátor, parametr)
	/// </summary>
	public enum SqlParseCommandComponent
	{
		/// <summary>
		/// Nejde o část SQL příkazu (jde o komentář, blank, atd)
		/// </summary>
		None = 1,
		/// <summary>
		/// Klíčové slovo (SELECT, INTO, UPDATE, FROM, JOIN, ON, AS, WHERE, ORDER, BY, 
		/// UNION, DELETE, INSERT, CASE, WHEN, THEN, ELSE, END, atd...)
		/// </summary>
		KeyWord,
		/// <summary>
		/// Textový parametr, například název sloupce, název tabulky, název proměnné, 
		/// </summary>
		Parameter,
		/// <summary>
		/// Výslovně uvedená textová hodnota
		/// </summary>
		String,
		/// <summary>
		/// Výslovně uvedená číselná hodnota
		/// </summary>
		Numeric,
		/// <summary>
		/// Výslovně uvedená proměnná / funkce / text (ani číslo, ani string)
		/// </summary>
		Literal,
		/// <summary>
		/// Operátory + - * / = větší menší ...
		/// </summary>
		Operator,
		/// <summary>
		/// Závorka levá, otevírací "(", každá je uvedena jednotlivě.
		/// </summary>
		BracketLeft,
		/// <summary>
		/// Závorka pravá, zavírací ")", každá je uvedena jednotlivě.
		/// </summary>
		BracketRight
	}
	#endregion
}
